/* 
 * Copyright (c) 2012 JSquirrelEngine
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in 
 * the Software without restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 */

package org.jsquirrel.map;

import org.jsquirrel.geometry.Polygon;
import org.jsquirrel.geometry.impl.ArrayPolygon;
import org.jsquirrel.math.Transform2D;


/**
 * Abstract class for a spatial data structure to store 2D Polygons in a rectangular space. Each
 * Polygon in Space is stored as a Space2DNode instance with an unique key.
 * 
 * @author Juan David Adarve Bermudez
 *
 * @param <K> Key for the nodes, it must be an implementation of java.lang.Comparable
 * @param <O> Kind of pay load to be stored with each node
 */
public abstract class Space2D<K extends Comparable<K>, O> {
	
	
	//*********************************************************************************************
	// INSTANCE ATTRIBUTES
	//*********************************************************************************************
	
	/**
	 * coordinates for the bottom-left corner of the space
	 */
	protected final float x0, y0;
	
	/**
	 * width and height of the space
	 */
	protected final float width, height;
	
	/**
	 * Polygon to represent the rectangular space, this Polygon is used for intersection checking with
	 * the nodes to insert. In case of no intersection, the node is outside of the space defined for this
	 * Space2D
	 */
	protected final Polygon spaceRectangle;
	
	/**
	 * Key generator for the nodes to be inserted
	 */
	protected final Space2DKeyGenerator<K> keyGenerator;
	
	
	
	
	//*********************************************************************************************
	// CONSTRUCTORS
	//*********************************************************************************************
	
	/**
	 * Creates a new Space2D with a rectangular space defined by the bottom left corner (0, 0) and
	 * the upper right corner (1, 1).
	 * 
	 * @param kGenerator key generator for the nodes to be created by this instance.
	 * 
	 * @throws NullPointerException when the key generator is null
	 */
	public Space2D(Space2DKeyGenerator<K> kGenerator) {
		
		if(kGenerator == null) {
			throw new NullPointerException("Space2D.Space2D(Space2DKeyGenerator<K>): kGenerator null");
		}
		
		x0 = 0;
		y0 = 0;
		width = 1;
		height = 1;
		
		// sets the key generator
		keyGenerator = kGenerator;
		
		// instantiates the rectangular space polygon
		spaceRectangle = new ArrayPolygon(4);		
		spaceRectangle.addVertex(x0, y0);
		spaceRectangle.addVertex(x0 + width, y0);
		spaceRectangle.addVertex(x0 + width, y0 + height);
		spaceRectangle.addVertex(x0, y0 + height);
	}
	
	/**
	 * Creates a new Space2D instance with a rectangular space given by the bottom left corner
	 * (x0, y0) and its width and height.
	 * 
	 * @param x0 X coordinate for the bottom left corner
	 * @param y0 Y coordinate for the bottom left corner
	 * @param width of rectangle. In case of width <= 0, an {@link IllegalArgumentException} is thrown
	 * @param height of rectangle. In case of height <= 0, and {@link IllegalArgumentException} is thrown
	 * @param kGenerator key generator for the nodes to be created by this instance. A {@link NullPointerException} is thrown
	 * if the key generator is null
	 * 
	 * @throws IllegalArgumentException if width <= 0 or height <= 0
	 * @throws NullPointerException if the key generator is null.
	 */
	public Space2D(float x0, float y0, float width, float height, Space2DKeyGenerator<K> kGenerator) {
		
		if(kGenerator == null) {
			throw new NullPointerException("Space2D.Space2D(float, float, float, float, Space2DKeyGenerator<K>): kGenerator null");
		}
		
		if(width <= 0) {
			throw new IllegalArgumentException("Space2D.Space2D(float, float, float, float, Space2DKeyGenerator<K>): invalid width: " + width);
		}
		
		if(height <= 0) {
			throw new IllegalArgumentException("Space2D.Space2D(float, float, float, float, Space2DKeyGenerator<K>): invalid height: " + height);
		}
		
		this.x0 = x0;
		this.y0 = y0;
		this.width = width;
		this.height = height;
		
		// sets the key generator
		keyGenerator = kGenerator;
		
		// instantiates the rectangular space polygon
		spaceRectangle = new ArrayPolygon(4);
		spaceRectangle.addVertex(x0, y0);
		spaceRectangle.addVertex(x0 + width, y0);
		spaceRectangle.addVertex(x0 + width, y0 + height);
		spaceRectangle.addVertex(x0, y0 + height);
	}

	
	//*********************************************************************************************
	// FINAL METHODS
	//*********************************************************************************************
	
	public final float getX0() {
		return x0;
	}
	
	public final float getY0() {
		return y0;
	}
	
	public final float getWidth() {
		return width;
	}
	
	public final float getHeight() {
		return height;
	}
	
	//*********************************************************************************************
	// ABSTRACT METHODS
	//*********************************************************************************************	
	
	/**
	 * Creates a new Space2DNode to insert the Polygon p on this Space2D. An implementation of this
	 * method must check whether p is null and generates a new and unique key for the new node.
	 * 
	 * @param p Polygon to be inserted on this Space2D
	 * 
	 * @throws NullPointerException if p is null
	 * 
	 */
	public abstract Space2DNode<K, O> createNode(final Polygon p);
	
	/**
	 * Inserts a Space2DNode previously created by calling the {@code createNode(Polygon p)}} on this instance.
	 * 
	 * @param node to be inserted.
	 * 
	 * @throws NodeOutOfSpaceBoundsException in case of node being outside the space defined for this instance
	 * @throws NullPointerException when node is null
	 */
	public abstract void insertNode(Space2DNode<K, O> node) throws NodeOutOfSpaceBoundsException;
	
	/**
	 * Remove the node from this instance which key is equal to the parameter.
	 * 
	 * @param key of node to be deleted
	 * 
	 * @return true if the node has been removed, false otherwise
	 */
	public abstract boolean removeNode(final K key);
	
	/**
	 * Adds an intersection listener to this instance.
	 * 
	 * @param listener
	 * 
	 * @throws NullPointerException if listener equal null
	 */
	public abstract void addIntersectionListener(final Space2DNodeIntersectionListener<K, O> listener);
	
	/**
	 * Retrieves from space the node whose key equals the one passed as parameter. The node is not
	 * removed from the spatial data structure and any modification of the inner node Polygon
	 * shall not be done.
	 * 
	 * @param key of the node to be retrieved
	 * 
	 * @return the node retrieved from space or null if it is not found.
	 */
	public abstract Space2DNode<K, O> queryByKey(final K key);
	
	/**
	 * Retrieves the nodes that are inside a rectangular query area.
	 * 
	 * @param x coordinate of the query rectangle bottom-left corner
	 * @param y coordinate of the query rectangle bottom-left corner
	 * @param width of query rectangle
	 * @param height of query rectangle
	 * 
	 * @return an instance of {@link Iterable} with the list of all nodes into the query rectangle
	 */
	public abstract Iterable<Space2DNode<K, O>> queryByRectangle(float x, float y, float width, float height);
	
	/**
	 * Retrieves the nodes that intersects the given query polygon p.
	 * 
	 * @param p query polygon
	 * 
	 * @return an instance of {@link Iterable} with the list of all nodes into the query rectangle
	 */
	public abstract Iterable<Space2DNode<K, O>> queryByPolygon(final Polygon p);
	
	/**
	 * Puts on limbo the node which key is equal to the parameter.
	 * 
	 * @param key to query
	 * 
	 * @return true if the node has been put on limbo, false otherwise
	 */
	public abstract boolean putOnLimboByKey(final K key);
	
	
	/**
	 * Puts on limbo all the nodes that intersects the rectangle defined by the
	 * parameters.
	 * 
	 * @param x coordinate of the rectangle bottom-left corner
	 * @param y coordinate of the rectangle bottom-left corner
	 * @param width of the query rectangle
	 * @param height of the query rectangle
	 * 
	 * @return true if at least one node has been put on limbo, false otherwise.
	 */
	public abstract boolean putOnLimboByRectangle(float x, float y, float width, float height);
	
	/**
	 * Puts on limbo all the nodes that intersects the Polygon parameter
	 * 
	 * @param p polygon to query
	 * 
	 * @return true if at least one node has been put on limbo, false otherwise.
	 */
	public abstract boolean putOnLimboByPolygon(final Polygon p);
	
	/**
	 * deletes all nodes present at limbo.
	 */
	public abstract void clearLimbo();
	
	/**
	 * Apply the Transform2D t to all nodes present at limbo.
	 * 
	 * @param t Transform2D to be applied
	 */
	public abstract void updateLimbo(final Transform2D t);
	
	/**
	 * flush all the nodes at limmbo and reinsert them on the Space data structure
	 */
	public abstract void flushLimbo();

}
